/*
 * LogEx a Java library for extracting log file data
 * Copyright 2012 LogEx Team.
 *
 * This program is free software. You can distribute or modify it under the 
 * terms of the GNU Lesser General Public License version 3.0 as published by  
 * the Free Software Foundation.
 *
 * This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
 * INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, contact the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.logex.utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.xquery.XQException;


import org.ikayzo.sdl.SDL;
import org.ikayzo.sdl.SDLParseException;
import org.ikayzo.sdl.Tag;
import org.logex.main.AsciiParser;
import org.logex.main.LineDetector;
import org.logex.main.RootObject;
import org.logex.main.StringConstants;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

/**
*
* @author Sasika Chandrasekara
*/

public class RemoteFile {

    private String host;
    private String username;
    private Session session;
    private String remoteFile;
    private String lineString;
    private String sdlFile;
    private String remoteFilePath;

    private InputStream inputStream;
    private OutputStream outputStream;

    private String fileName;
    private int count;
    private AsciiParser asciiParser;
    private LineDetector lineDetector;


    /**
	 * Constructor for <code>RemoteFile</code> class. Extracts username, host, and 
	 * log file name from path. <code>remoteFilePath</code> should be declared as 
	 * username@host:file_path.
	 * 
	 * @param logFilePath
	 *            Name (with path) of the log file. 
	 * @param sdlFile
	 *            Name of the corresponding SDL file (no path needed, file needs
	 *            to be put into SDL folder)
	 */
    public RemoteFile(String remoteFilePath, String sdlFile) {
    	this.sdlFile = sdlFile;
    	this.remoteFilePath = remoteFilePath;
    	username = remoteFilePath.substring(0, remoteFilePath.indexOf("@"));
    	host = remoteFilePath.substring(remoteFilePath.indexOf("@") + 1, remoteFilePath.indexOf(":"));
    	remoteFile = remoteFilePath.substring(remoteFilePath.indexOf(":") + 1);
		fileName = remoteFile.substring(remoteFile.lastIndexOf(File.separator) + 1);
        asciiParser = new AsciiParser(fileName);
        lineDetector = new LineDetector();
    }

    public static void main(String[] args) throws IOException, SDLParseException, JSchException, XQException {
		String remoteFileName = "Sasika@localhost:/media/681CBF6C1CBF33C2/Documents\\ and\\ Settings/Sasika/Documents/access.log";
		String sdlFile = "access.sdl";
		RemoteFile remoteFile = new RemoteFile(remoteFileName, sdlFile);
		remoteFile.readRemoteFile();
	}
    
    /**
     * This method reads the log file in a remote host and extracts data from it
     * and stores the extracted data in a database.
     *
     * @throws IOException Due to errors in writing in / reading from files
     * @throws SDLParseException If the SDL format is malformed
     * @throws JSchException If there were problems creating session with the remote host
     * @throws XQException If reasons such as wrong commands, DB errors occur
     *
     */
    public void readRemoteFile() throws IOException, SDLParseException, JSchException, XQException {

            setRootObject();
            Session session = createSession();
            createChannel();

            byte[] buf = new byte[1024];

            buf[0] = 0; outputStream.write(buf, 0, 1); outputStream.flush();

            getDataFromStream(buf);

            session.disconnect();
            System.exit(0);

    }

    /**
     *
     * Sets <code>rootObject</code> in the <code>parserReader</code> which resulted
     * by parsing the sdl file which corresponds to the particular log file.
     *
     * @throws IOException Due to errors occurred while reading the file.
     * @throws SDLParseException SDL file is malformed.
     *
     */
    private void setRootObject() throws IOException, SDLParseException {
        File ee = new File(StringConstants.SDL_FOLDER_NAME + File.separator + sdlFile);
        Tag root1 = SDL.read(ee);
        asciiParser.setRootObject(new RootObject(root1));
    }

    /**
     *
     * Creates the session using with host using the given username on port 22.
     * The password for the given username will be asked using the
     * <code>MUserInfo</code> object.
     *
     * @throws JSchException Due to errors occurred while creating the session.
     */
    private Session createSession() throws JSchException {
        JSch jsch = new JSch();
        session = jsch.getSession(username, host, 22);

        session.setUserInfo(new MUserInfo());
        session.connect();
        return session;
    }

    /**
     *
     * Creates a channel using the session created by the <code>createSession()</code>
     * method. It runs the <code>scp -f</code> command on the remote file while
     * the channel is being created.
     *
     * @see createSession()
     */
    private void createChannel() throws JSchException, IOException {
        String command = "scp -f "+remoteFile;
        Channel channel = session.openChannel("exec");
        ((ChannelExec)channel).setCommand(command);

        outputStream = channel.getOutputStream();
        inputStream = channel.getInputStream();

        channel.connect();
    }

    /**
     *
     * Initialize the data reading part from the remote file. It calls the
     * <code>readFileSize()</code> method read data from file. After file is
     * read completely <code>lastreadline.txt</code> is updated accordingly.
     *
     * @param buf character array to be used as buffer for reading and writing purposes
     *
     * @throws IOException Due to errors occurred while reading the file.
     * @throws XQException If reasons such as wrong commands, DB errors occur
     *
     * @see getFileName()
     * 
     */
    private void getDataFromStream(byte[] buf) throws IOException, XQException{
        while(true) {
            int c = checkAck(inputStream);
            if(c != 'C'){
                break;
            }

            inputStream.read(buf, 0, 5);

            getFileSize(buf);
            fileName = getFileName(buf);

            buf[0] = 0; outputStream.write(buf, 0, 1); outputStream.flush();

            readFileData();
            
            lineDetector.writeToFile(count, remoteFilePath);
            buf[0] = 0; outputStream.write(buf, 0, 1); outputStream.flush();
        }
    }

    /**
     *
     * Reading the file size which is coming through the <code>inputStream</code>.
     *
     * @param buf character array to be used as buffer for reading purposes
     *
     * @throws IOException Due to errors occurred while reading the file.
     *
     */
    private long getFileSize(byte[] buf) throws IOException {
        long filesize = 0L;

        while(true) {
            if(inputStream.read(buf, 0, 1) < 0){
                break;
            }
            if(buf[0] == ' ')break;
            filesize = filesize*10L + (long)(buf[0] - '0');
        }
        return filesize;
    }

     /**
     *
     * Reading the file name which is coming through the <code>inputStream</code>.
     *
     * @param buf character array to be used as buffer for reading purposes
     *
     * @throws IOException Due to errors occurred while reading the file.
     *
     */
    private String getFileName(byte[] buf) throws IOException {
        String file = null;
        for(int i=0;;i++){
            inputStream.read(buf, i, 1);
            if(buf[i]==(byte)0x0a){
                file=new String(buf, 0, i);
                break;
            }
        }
        return file;
    }

    /**
     *
     * Read data from file using the <code>inputStream</code> and after reading
     * a line from the file it has been sent to <code>parserReader</code> for
     * further processing and storing it in database.
     *
     * @throws IOException Due to errors occurred while reading the file.
     * @throws XQException If reasons such as wrong commands, DB errors occur
     * 
     */
    private void readFileData() throws IOException, XQException {
        int character;
        int lineCount = lineDetector.checkFile(remoteFilePath);
        count = 0;
        asciiParser.prepareLists();
        while ((character = inputStream.read()) > 0) {
        	count++;   
        	if (lineCount >= count) {
                        continue;
        	} else {
                        StringBuffer sb = new StringBuffer();
                        sb.append((char) character);
                        character = inputStream.read();
                        while (character != '\n') {
                            sb.append((char) character);
                            character = inputStream.read();
                        }
                        sb.append((char) character);
                        lineString = sb.toString();
                        asciiParser.actOnLine(lineString);
        	}
        }
    }

    /**
     *
     * This checks the acknowledgement through the <code>inputStream</code> after
     * data is sent through the <code>outputStream</code>.
     *
     * @param inputStream InputStream to check the acknowledgement
     *
     * @return int value between -1 to 255 reading the first character value of
     *          the acknowledgement
     */
    private int checkAck(InputStream inputStream) throws IOException{
        int b = inputStream.read();
        if(b==0) return b;
        if(b==-1) return b;

        if(b==1 || b==2) {
            StringBuilder sb=new StringBuilder();
            int charValue;
            do {
                charValue=inputStream.read();
                sb.append((char)charValue);
            }
            while(charValue!='\n');
            if(b==1) { 
                System.out.print(sb.toString());
            }
            if(b==2) { 
                System.out.print(sb.toString());
            }
        }
        return b;
    }
		
}
